package cn.bugDance.infrastructure.persistent.repository;

import cn.bugDance.domain.credit.model.aggregate.CreditAdjustAggregate;
import cn.bugDance.domain.credit.model.entity.TaskEntity;
import cn.bugDance.domain.credit.model.entity.UserCreditAccountEntity;
import cn.bugDance.domain.credit.model.entity.UserCreditOrderEntity;
import cn.bugDance.domain.credit.model.valobj.UserCreditAccountStateVO;
import cn.bugDance.domain.credit.repository.ICreditRepository;
import cn.bugDance.infrastructure.event.EventPublisher;
import cn.bugDance.infrastructure.persistent.dao.ITaskDao;
import cn.bugDance.infrastructure.persistent.dao.IUserCreditAccountDao;
import cn.bugDance.infrastructure.persistent.dao.IUserCreditOrderDao;
import cn.bugDance.infrastructure.persistent.po.Task;
import cn.bugDance.infrastructure.persistent.po.UserCreditAccount;
import cn.bugDance.infrastructure.persistent.po.UserCreditOrder;
import cn.bugDance.infrastructure.persistent.redis.IRedisService;
import cn.bugDance.types.common.Constants;
import cn.bugDance.types.enums.ResponseCode;
import cn.bugDance.types.exception.AppException;
import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Repository
@Slf4j
public class CreditRepository implements ICreditRepository {

    @Resource
    private IUserCreditAccountDao userCreditAccountDao;
    @Resource
    private IDBRouterStrategy dbRouter;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private IRedisService redisService;
    @Resource
    private IUserCreditOrderDao userCreditOrderDao;
    @Resource
    private ITaskDao taskDao;
    @Resource
    private EventPublisher eventPublisher;


    @Override
    public void createUserCreditOrder(CreditAdjustAggregate creditAdjustAggregate) {
        String userId = creditAdjustAggregate.getUserId();
        UserCreditAccountEntity userCreditAccountEntity = creditAdjustAggregate.getUserCreditAccountEntity();
        UserCreditOrderEntity userCreditOrderEntity = creditAdjustAggregate.getUserCreditOrderEntity();
        TaskEntity taskEntity = creditAdjustAggregate.getTaskEntity();

        String lockKey = Constants.Redis.USER_CREDIT_ACCOUNT_LOCK + userId + Constants.UNDERLINE + userCreditOrderEntity.getBizId();
        RLock lock = redisService.getLock(lockKey);

        try{
            lock.lock(3, TimeUnit.SECONDS);
            dbRouter.doRouter(userId);
            UserCreditOrder userCreditOrder = new UserCreditOrder();;
            userCreditOrder.setUserId(userId);
            userCreditOrder.setBizId(userCreditOrderEntity.getBizId());
            userCreditOrder.setOrderId(userCreditOrderEntity.getOrderId());
            userCreditOrder.setTradeTime(userCreditOrderEntity.getTradeTime());
            userCreditOrder.setTradeName(userCreditOrderEntity.getTradeName().getCode());
            userCreditOrder.setTradeType(userCreditOrderEntity.getTradeType().getCode());
            userCreditOrder.setTradeAmount(userCreditOrderEntity.getTradeAmount());

            UserCreditAccount userCreditAccountReq = new UserCreditAccount();
            userCreditAccountReq.setUserId(userId);
            userCreditAccountReq.setTotalAmount(userCreditAccountEntity.getTotalAmount());
            userCreditAccountReq.setAvailableAmount(userCreditAccountEntity.getAvailableAmount());

            Task task = new Task();
            task.setUserId(taskEntity.getUserId());
            task.setTopic(taskEntity.getTopic());
            task.setState(taskEntity.getState().getCode());
            task.setMessage(JSON.toJSONString(taskEntity.getMessage()));
            task.setMessageId(taskEntity.getMessage().getId());

            transactionTemplate.execute(status -> {
                try{
                    UserCreditAccount userCreditAccount = userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
                    if(userCreditAccount == null){
                        userCreditAccountDao.insert(userCreditAccountReq);
                    }
                    else{
                        userCreditAccountDao.updateUserCreditAccount(userCreditAccountReq);
                    }
                    userCreditOrderDao.insert(userCreditOrder);
                    taskDao.insert(task);
                } catch (DuplicateKeyException e){
                    log.error("【CreditRepository->】: 创建积分变更订单， 唯一索引冲突, userId: {}, orderId: {}", userId, userCreditOrderEntity.getOrderId());
                    status.setRollbackOnly();
                    throw new AppException(ResponseCode.DUPLICATE_KEY_EXCEPTION.getCode(), ResponseCode.DUPLICATE_KEY_EXCEPTION.getInfo());
                }catch (Exception e){
                    log.error("【CreditRepository->】: 创建积分变更订单失败, userId: {}, orderId: {}", userId, userCreditOrderEntity.getOrderId());
                    status.setRollbackOnly();
                    throw e;
                }
                return 1;
            });
        }finally {
            dbRouter.clear();
            lock.unlock();
        }

        // 发送MQ支付成功通知任务
        try{
            eventPublisher.publish(taskEntity.getTopic(), taskEntity.getMessage());
            dbRouter.doRouter(taskEntity.getUserId());
            taskDao.updateTaskStateSuccess(taskEntity.getMessage().getId());
        }catch (Exception e){
            log.warn("【CreditRepository->】积分交易支付成功MQ消息发送失败, 等待补偿任务");
            taskDao.updateTaskStateFail(taskEntity.getMessage().getId());
        }finally {
            dbRouter.clear();
        }

    }

    @Override
    public UserCreditAccountEntity queryUserCreditAccount(String userId) {
        try{
            UserCreditAccount userCreditAccountReq = new UserCreditAccount();
            userCreditAccountReq.setUserId(userId);
            dbRouter.doRouter(userId);

            UserCreditAccount userCreditAccount = userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
            if(null == userCreditAccount) return null;
            UserCreditAccountEntity userCreditAccountEntity = new UserCreditAccountEntity();
            userCreditAccountEntity.setUserId(userId);
            userCreditAccountEntity.setTotalAmount(userCreditAccount.getTotalAmount());
            userCreditAccountEntity.setAvailableAmount(userCreditAccount.getAvailableAmount());
            userCreditAccountEntity.setState(UserCreditAccountStateVO.valueOf(userCreditAccount.getState()));

            return userCreditAccountEntity;
        }finally {
            dbRouter.doRouter(userId);
        }
    }

    @Override
    public Integer queryAvailableAmount(String userId) {
        return 0;
    }

    @Override
    public void createUserCreditOrderRebate(CreditAdjustAggregate creditAdjustAggregate) {
        String userId = creditAdjustAggregate.getUserId();
        UserCreditAccountEntity userCreditAccountEntity = creditAdjustAggregate.getUserCreditAccountEntity();
        UserCreditOrderEntity userCreditOrderEntity = creditAdjustAggregate.getUserCreditOrderEntity();


        String lockKey = Constants.Redis.USER_CREDIT_ACCOUNT_LOCK + userId + Constants.UNDERLINE + userCreditOrderEntity.getBizId();
        RLock lock = redisService.getLock(lockKey);

        try{
            lock.lock(3, TimeUnit.SECONDS);
            dbRouter.doRouter(userId);
            UserCreditOrder userCreditOrder = new UserCreditOrder();;
            userCreditOrder.setUserId(userId);
            userCreditOrder.setBizId(userCreditOrderEntity.getBizId());
            userCreditOrder.setOrderId(userCreditOrderEntity.getOrderId());
            userCreditOrder.setTradeTime(userCreditOrderEntity.getTradeTime());
            userCreditOrder.setTradeName(userCreditOrderEntity.getTradeName().getCode());
            userCreditOrder.setTradeType(userCreditOrderEntity.getTradeType().getCode());
            userCreditOrder.setTradeAmount(userCreditOrderEntity.getTradeAmount());

            UserCreditAccount userCreditAccountReq = new UserCreditAccount();
            userCreditAccountReq.setUserId(userId);
            userCreditAccountReq.setTotalAmount(userCreditAccountEntity.getTotalAmount());
            userCreditAccountReq.setAvailableAmount(userCreditAccountEntity.getAvailableAmount());


            transactionTemplate.execute(status -> {
                try{
                    UserCreditAccount userCreditAccount = userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
                    if(userCreditAccount == null){
                        userCreditAccountDao.insert(userCreditAccountReq);
                    }
                    else{
                        userCreditAccountDao.updateUserCreditAccount(userCreditAccountReq);
                    }
                    userCreditOrderDao.insert(userCreditOrder);
                } catch (DuplicateKeyException e){
                    log.error("【CreditRepository->】: 创建积分变更订单， 唯一索引冲突, userId: {}, orderId: {}", userId, userCreditOrderEntity.getOrderId());
                    status.setRollbackOnly();
                    throw new AppException(ResponseCode.DUPLICATE_KEY_EXCEPTION.getCode(), ResponseCode.DUPLICATE_KEY_EXCEPTION.getInfo());
                }catch (Exception e){
                    log.error("【CreditRepository->】: 创建积分变更订单失败, userId: {}, orderId: {}", userId, userCreditOrderEntity.getOrderId());
                    status.setRollbackOnly();
                    throw e;
                }
                return 1;
            });
        }finally {
            dbRouter.clear();
            lock.unlock();
        }
    }
}
